/*
 * Copyright (c) 2018, vindell (https://github.com/vindell).
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.github.vindell.dbperms.jdbc.common;

import java.sql.Blob;
import java.text.SimpleDateFormat;

/**
 * Value holder of the data passed to DB as well as of those retrieved capable
 * of binary data logging depending on the configuration property
 * {@code excludebinary}.
 *
 * @author Peter Butkovic
 *
 */
public class Value {

	private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
			'F' };

	/**
	 * Value itself.
	 */
	private Object value;

	public Value(Object valueToSet) {
		this();
		this.value = valueToSet;
	}

	public Value() {
	}

	public Object getValue() {
		return value;
	}

	public void setValue(Object value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return convertToString(this.value);
	}

	/**
	 * Returns the {@link String} representation of the given value depending on the
	 * value type. Formats:
	 * <ul>
	 * <li>{@link java.util.Date} values it in a way configured via configuration
	 * property: {@code dateformat},</li>
	 * <li>{@code byte[]} values are converted to {@link String} Hexadecimal
	 * representation, unless configuration property {@code exclidebinary=true} is
	 * set.</li>
	 * <li>for other types string representation is simply returned.</li>
	 * </ul>
	 *
	 * @param value
	 * @return
	 */
	public String convertToString(Object value) {
		String result = null;
		if (value == null) {
			result = "NULL";
		} else {

			if (value instanceof java.util.Date) {
				result = new SimpleDateFormat("").format(value);
			} else if (value instanceof Boolean) {
				if ("numeric".equals("")) {
					result = Boolean.FALSE.equals(value) ? "0" : "1";
				} else {
					result = value.toString();
				}
			} else if (value instanceof byte[]) {
				/*
				 * if (P6LogOptions.getActiveInstance().getExcludebinary()) { result =
				 * "[binary]"; } else { result = toHexString((byte[]) value); }
				 */

				// we should not do ((Blob) value).getBinaryStream(). ...
				// as inputstream might not be re-rea
				// } else if (value instanceof Blob) {
				// if (P6LogOptions.getActiveInstance().getExcludebinary()) {
				// result = "[binary]";
				// } else {
				// result = value.toString();
				// }
			} else {
				result = value.toString();
			}

			result = quoteIfNeeded(result, value);
		}

		return result;
	}

	/**
	 * @param bytes
	 *            the bytes value to convert to {@link String}
	 * @return the hexadecimal {@link String} representation of the given
	 *         {@code bytes}.
	 */
	private String toHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder();
		for (byte b : bytes) {
			int temp = (int) b & 0xFF;
			sb.append(HEX_CHARS[temp / 16]);
			sb.append(HEX_CHARS[temp % 16]);
		}
		return sb.toString();
	}

	/**
	 * Qoutes the passed {@code stringValue} if it's needed.
	 * 
	 * @param stringValue
	 * @param obj
	 * @return
	 */
	private String quoteIfNeeded(String stringValue, Object obj) {
		if (stringValue == null) {
			return null;
		}

		/*
		 * The following types do not get quoted: numeric, boolean
		 * 
		 * It is tempting to use ParameterMetaData.getParameterType() for this purpose
		 * as it would be safer. However, this method will fail with some JDBC drivers.
		 * 
		 * Oracle: Not supported until ojdbc7 which was released with Oracle 12c.
		 * https://forums.oracle.com/thread/2584886
		 * 
		 * MySQL: The method call only works if service side prepared statements are
		 * enabled. The URL parameter 'useServerPrepStmts=true' enables.
		 */
		if (Number.class.isAssignableFrom(obj.getClass()) || Boolean.class.isAssignableFrom(obj.getClass())) {
			return stringValue;
		} else {
			return "'" + escape(stringValue) + "'";
		}
	}

	/**
	 * Escapes special characters in SQL values. Currently is only {@code '} escaped
	 * with {@code ''}.
	 * 
	 * @param stringValue
	 *            value to escape
	 * @return escaped value.
	 */
	private String escape(String stringValue) {
		return stringValue.replaceAll("'", "''");
	}

}
